/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.copier.internal;

import java.util.*;
import ch.trackedbean.copier.*;

/**
 * Default implementation of {@link IMappingContext}.
 * 
 * @author M. Hautle
 */
public class DefaultMappingContext implements IMappingContext {
    /** The mapping filter to use. */
    protected final IMappingFilter filter;

    /** The cache for the get and put calls. */
    protected final IdentityHashMap<Object, Object[]> cache = new IdentityHashMap<Object, Object[]>();

    /** Map for context dependent values. Default is {@link Collections#EMPTY_MAP} cause only few calls will use this feature. */
    protected Map<Object, Object> config = Collections.emptyMap();

    /**
     * Default constructor.
     * 
     * @param filter The mapping filter to use
     */
    public DefaultMappingContext(IMappingFilter filter) {
        this.filter = filter;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean copyDst2Src(IMappingHolder holder) {
        return filter.copyDst2Src(holder);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean copySrc2Dst(IMappingHolder holder) {
        return filter.copySrc2Dst(holder);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getElement(Object key, Class<T> type) {
        final Object[] val = cache.get(key);
        // no value for the key
        if (val == null)
            return null;
        // lookup the entry for the specified key
        for (int i = 0; i < val.length; i++)
            if (val[i].getClass() == type)
                return (T) val[i];
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T putElement(Object key, T value) {
        final Object[] old = cache.put(key, new Object[] { value });
        if (old == null)
            return value;
        // the case that we have more than one value for the same key (which should happen rarely)
        final Object[] vals = new Object[old.length + 1];
        System.arraycopy(old, 0, vals, 0, old.length);
        vals[old.length] = value;
        cache.put(key, vals);
        return value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getValue(Object key) {
        return (T) config.get(key);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object putValue(Object key, Object value) {
        // empty == Collections.EMPTY_MAP; so we create the real config map (this is a kind of 'lazy' creation)
        if (config.isEmpty())
            config = new HashMap<Object, Object>();
        return config.put(key, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void stepIntoObject(Object value) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void stepOutOfObject() {
    }
}
